// Dynamic text widget implmenentation for Gtk+
// Copyright 2006 (C) Ralph Thomas

#include <view/dynamic_text.h>
#include <gtk/os_utilities.hpp>
#include <binder.hpp>
#include <gtk/gtk.h>

#include <iostream>

namespace view {
	//
	// This is a run-of-the-mill (totally inaccessible) text display with rename
	// support (like file/folder labels in Windows Explorer).
	//
	struct dynamic_text_t {
		typedef adobe::implementation::signal_wrapper_zero_t<void> on_change_t;
		typedef adobe::implementation::signal_wrapper_one_t<gboolean, GdkEventButton*> on_click_t;
		typedef adobe::implementation::signal_wrapper_one_t<gboolean, GdkEventFocus*> on_lose_focus_t;

		adobe::auto_ptr<GtkWidget>	eventbox_m;
		adobe::auto_ptr<GtkWidget>	control_m;
		adobe::auto_ptr<GtkWidget>	editable_m;
		on_click_t			on_mouse_down_m;
		on_click_t			on_mouse_up_m;
		on_lose_focus_t			on_lose_focus_m;
		adobe::binder_helper_t		datapump_m;
		guint32				mouse_down_time_m;

		void on_data_change( const adobe::value_t& value ) {
			//
			// A new value is coming down from the datapump.
			//
			try {
				std::string text( value.get<std::string>() );
				gtk_label_set_text( GTK_LABEL(control_m.get()), text.c_str() );
			} catch( ... ) {}
		}
		gboolean on_mouse_down( GdkEventButton* event ) {
			mouse_down_time_m = event->time; return false;
		}
		gboolean on_mouse_up( GdkEventButton* event ) {
			//
			// If the time was long enough then we show the editable
			// label and afford the user the opportunity to rename.
			// (If we are enabled, that is).
			//
			if( !GTK_WIDGET_IS_SENSITIVE(control_m.get()) ) return false;
			if( !mouse_down_time_m ) return false;
			if( event->time - mouse_down_time_m < 250 ) return false;
			//
			// Ensure that more clicks don't accidentally bring us here.
			//
			mouse_down_time_m = 0;
			//
			// Update the editable control with the most recent text,
			// make it visible and give it focus.
			//
			std::string text( gtk_label_get_text(GTK_LABEL(control_m.get())) );
			gtk_entry_set_text( GTK_ENTRY(editable_m.get()), text.c_str() );
			gtk_widget_show( editable_m.get() );
			gtk_widget_grab_focus( editable_m.get() );
			//
			// Select all of the text up to the last ".". This is a
			// hack and should be customizable.
			//
			// XXX: cast nastyness!
			//
			unsigned long i( text.rfind( "." ) );
			if( i == std::string::npos ) i = (unsigned long)-1;
			gtk_editable_select_region( GTK_EDITABLE(editable_m.get()), 0, (long)i );
			//
			// The editable label will now send us events such as
			// on_data_change and on_focus_lost.
			//
			return true;//false;
		}
		gboolean on_lose_focus( GdkEventFocus* ) {
			gtk_widget_hide( editable_m.get() );
			//
			// Push the update out now.
			//
			std::string text( gtk_entry_get_text( GTK_ENTRY(editable_m.get()) ) );
			gtk_label_set_text( GTK_LABEL(control_m.get()), text.c_str() );
			datapump_m.set( adobe::value_t( text ) );
			return false;
		}
		void place( const adobe::point_2d_t& pos, const adobe::extents_t& extents ) {
			adobe::implementation::set_bounds_widget( eventbox_m.get(), pos, extents );
			adobe::implementation::set_bounds_widget( editable_m.get(), pos, extents );
		}
		dynamic_text_t( adobe::sheet_t& sheet, const adobe::value_t& bind ) : datapump_m( sheet ), mouse_down_time_m( 0 ) {
			eventbox_m.reset( gtk_event_box_new() );
			control_m.reset( gtk_label_new( "" ) );
			editable_m.reset( gtk_entry_new() );
			//
			// Configure the event box, and put the label
			//
			gtk_event_box_set_visible_window( GTK_EVENT_BOX(eventbox_m.get()), false );
			gtk_widget_add_events( eventbox_m.get(), GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK );
			gtk_misc_set_alignment( GTK_MISC(control_m.get()), 0.0f, 0.5f );
			gtk_container_add( GTK_CONTAINER(eventbox_m.get() ), control_m.get() );
			gtk_widget_show( control_m.get() );
			//
			// Connect our datapump up.
			//
			datapump_m.initialize( bind, boost::bind( &dynamic_text_t::on_data_change, this, _1 ),
				boost::bind( &gtk_widget_set_sensitive, control_m.get(), _1 ) );
			//
			// Listen for all of the interesting events from our widgets.
			//
			on_mouse_down_m.set_callback( boost::bind( &dynamic_text_t::on_mouse_down, this, _1 ) );
			on_mouse_down_m.connect( eventbox_m.get(), "button-press-event" );
			on_mouse_up_m.set_callback( boost::bind( &dynamic_text_t::on_mouse_up, this, _1 ) );
			on_mouse_up_m.connect( eventbox_m.get(), "button-release-event" );
			on_lose_focus_m.set_callback( boost::bind( &dynamic_text_t::on_lose_focus, this, _1 ) );
			on_lose_focus_m.connect( editable_m.get(), "focus-out-event" );
		}
	};

	adobe::widget_node_t dynamic_text_factory(
		const adobe::dictionary_t& 	parameters,
		const adobe::widget_node_t&	parent,
		const adobe::factory_token_t&	token )
	{
		//
		// The dynamic_text widget must be bound to something.
		//
		adobe::value_t bind = parameters.at( adobe::static_name_t( "bind" ) );
		dynamic_text_t* w = new dynamic_text_t( token.sheet_m, bind );
		token.assemblage_m.delete_on_destruction( w );
		//
		// Insert the label into our view.
		//
		GtkWidget* label( w->eventbox_m.get() );
		GtkWidget* editable( w->editable_m.get() );
		adobe::display_t::position_t display_token( adobe::insert( adobe::get_main_display(), parent.display_token_m, label ) );
		adobe::insert( adobe::get_main_display(), parent.display_token_m, editable );
		gtk_widget_hide( editable );
		adobe::display_t::position_t overlay_token( token.overlay_m.insert( parent.overlay_token_m ) );
		//
		// The best bounds calculate the best bounds in terms
		// of a minimum number of characters.
		//
		unsigned int min_chars = 10;
		parameters.get( adobe::static_name_t( "characters" ), min_chars );
		GtkLabel* control( GTK_LABEL( w->control_m.get() ) );
		//
		// edit_text is measured in terms of the "0" character,
		// this is something of an adobe standard.
		//
		std::string sample_text( min_chars, '0' );
		if( min_chars == 0 ) min_chars++;
		std::string original_text( gtk_label_get_label( control ) );
		gtk_label_set_text( control, sample_text.c_str() );
		adobe::eve_t::signal_suite_t signals;
		eve_client::place_func_t placer = boost::bind( &dynamic_text_t::place, w, _1, _2 );
		signals.eve_calculate_proc_m = boost::bind( &eve_client::utility_widget_calculate, adobe::implementation::best_bounds_widget( GTK_WIDGET(control) ), _1 );
		signals.eve_place_proc_m = boost::bind( &eve_client::utility_widget_place, placer, _1, _2, boost::ref( token.overlay_m ), overlay_token );
		gtk_label_set_text( control, original_text.c_str() );
		//
		// Register the widget with Eve.
		//
		adobe::eve_t::iterator eve_token(
			token.layout_m.add_view_element( parent.eve_token_m,
				adobe::eve_t::insert_element_t(
					adobe::eve_t::calculate_data_t(),
					false,
					parameters,
					signals )
				)
			);
		//
		// Return the new icon view.
		//
		return adobe::widget_node_t( parent.size_m, eve_token, display_token, overlay_token );
	}
}
